1 using System;
2 using System.Collections.Generic;
3 using UnityEngine;
4 using UnityEngine.Assertions;
5
6 namespace ProceduralToolkit.Examples
7 {
8 public static class RoofGenerator
9 {
10 private const float GabledRoofHeight = 2;
11 private const float HippedRoofHeight = 2;
12
13 public static MeshDraft Generate(
14 List<Vector2> foundationPolygon,
15 float roofHeight,
16 RoofConfig roofConfig)
17 {
18 List<Vector2> roofPolygon = OffsetPolygon(foundationPolygon, -roofConfig.overhang);
19
20 MeshDraft roofDraft;
21 switch (roofConfig.type)
22 {
23 case RoofType.Flat:
24 roofDraft = GenerateFlat(roofPolygon, roofConfig);
25 break;
26 case RoofType.Gabled:
27 roofDraft = GenerateGabled(roofPolygon, roofConfig);
28 break;
29 case RoofType.Hipped:
30 roofDraft = GenerateHipped(roofPolygon, roofConfig);
31 break;
32 default:
33 throw new ArgumentOutOfRangeException();
34 }
35
36 if (roofConfig.thickness > 0)
37 {
38 roofDraft.Add(GenerateBorder(roofPolygon, roofConfig));
39 }
40
41 if (roofConfig.overhang > 0)
42 {
43 roofDraft.Add(GenerateOverhang(foundationPolygon, roofPolygon));
44 }
45
46 roofDraft.Move(Vector3.up*roofHeight);
47 roofDraft.uv.Clear();
48 return roofDraft;
49 }
50
51 private static MeshDraft GenerateFlat(List<Vector2> roofPolygon, RoofConfig roofConfig)
52 {
53 Vector3 a = roofPolygon[0].ToVector3XZ() + Vector3.up*roofConfig.thickness;
54 Vector3 b = roofPolygon[1].ToVector3XZ() + Vector3.up*roofConfig.thickness;
55 Vector3 c = roofPolygon[2].ToVector3XZ() + Vector3.up*roofConfig.thickness;
56 Vector3 d = roofPolygon[3].ToVector3XZ() + Vector3.up*roofConfig.thickness;
57
58 var roofDraft = MeshDraft.Quad(a, d, c, b);
59 return roofDraft;
60 }
61
62 public static MeshDraft GenerateGabled(List<Vector2> roofPolygon, RoofConfig roofConfig)
63 {
64 Vector3 a = roofPolygon[0].ToVector3XZ() + Vector3.up*roofConfig.thickness;
65 Vector3 b = roofPolygon[1].ToVector3XZ() + Vector3.up*roofConfig.thickness;
66 Vector3 c = roofPolygon[2].ToVector3XZ() + Vector3.up*roofConfig.thickness;
67 Vector3 d = roofPolygon[3].ToVector3XZ() + Vector3.up*roofConfig.thickness;
68
69 Vector3 ridgeHeight = Vector3.up*GabledRoofHeight;
70 Vector3 ridge0 = (a + d)/2 + ridgeHeight;
71 Vector3 ridge1 = (b + c)/2 + ridgeHeight;
72
73 var roofDraft = MeshDraft.Quad(a, ridge0, ridge1, b);
74 roofDraft.Add(MeshDraft.Triangle(b, ridge1, c));
75 roofDraft.Add(MeshDraft.Quad(c, ridge1, ridge0, d));
76 roofDraft.Add(MeshDraft.Triangle(d, ridge0, a));
77 return roofDraft;
78 }
79
80 public static MeshDraft GenerateHipped(List<Vector2> roofPolygon, RoofConfig roofConfig)
81 {
82 Vector3 a = roofPolygon[0].ToVector3XZ() + Vector3.up*roofConfig.thickness;
83 Vector3 b = roofPolygon[1].ToVector3XZ() + Vector3.up*roofConfig.thickness;
84 Vector3 c = roofPolygon[2].ToVector3XZ() + Vector3.up*roofConfig.thickness;
85 Vector3 d = roofPolygon[3].ToVector3XZ() + Vector3.up*roofConfig.thickness;
86
87 Vector3 ridgeHeight = Vector3.up*HippedRoofHeight;
88 Vector3 ridgeOffset = (b - a).normalized*2;
89 Vector3 ridge0 = (a + d)/2 + ridgeHeight + ridgeOffset;
90 Vector3 ridge1 = (b + c)/2 + ridgeHeight - ridgeOffset;
91 var roofDraft = MeshDraft.Quad(a, ridge0, ridge1, b);
92 roofDraft.Add(MeshDraft.Triangle(b, ridge1, c));
93 roofDraft.Add(MeshDraft.Quad(c, ridge1, ridge0, d));
94 roofDraft.Add(MeshDraft.Triangle(d, ridge0, a));
95 return roofDraft;
96 }
97
98 private static MeshDraft GenerateBorder(List<Vector2> roofPolygon, RoofConfig roofConfig)
99 {
100 List<Vector3> lowerRing = roofPolygon.ConvertAll(v => v.ToVector3XZ());
101 List<Vector3> upperRing = roofPolygon.ConvertAll(v => v.ToVector3XZ() + Vector3.up*roofConfig.thickness);
102 var border = MeshDraft.FlatBand(lowerRing, upperRing);
103 return border;
104 }
105
106 private static MeshDraft GenerateOverhang(List<Vector2> foundationPolygon, List<Vector2> roofPolygon)
107 {
108 List<Vector3> lowerRing = foundationPolygon.ConvertAll(v => v.ToVector3XZ());
109 List<Vector3> upperRing = roofPolygon.ConvertAll(v => v.ToVector3XZ());
110 var overhang = MeshDraft.FlatBand(lowerRing, upperRing);
111 return overhang;
112 }
113
114 private static List<Vector2> OffsetPolygon(List<Vector2> polygon, float distance)
115 {
116 var newPolygon = new List<Vector2>();
117 for (int i = 0; i < polygon.Count; i++)
118 {
119 var previous = polygon.GetLooped(i - 1);
120 var current = polygon[i];
121 var next = polygon.GetLooped(i + 1);
122 float angle;
123 Vector2 bisector = GetBisector(previous, current, next, out angle);
124 float hypotenuse = distance/GetBisectorSin(angle);
125
126 newPolygon.Add(current + bisector*hypotenuse);
127 }
128 return newPolygon;
129 }
130
131 private static Vector2 GetBisector(Vector2 previous, Vector2 current, Vector2 next, out float angle)
132 {
133 Vector2 toPrevious = (previous - current).normalized;
134 Vector2 toNext = (next - current).normalized;
135
136 angle = PTUtils.Angle360(toPrevious, toNext);
137 Assert.IsFalse(float.IsNaN(angle));
138 return toPrevious.RotateCW(angle/2);
139 }
140
141 private static float GetBisectorSin(float angle)
142 {
143 if (angle > 180)
144 {
145 angle = 360 - angle;
146 }
147 return Mathf.Sin(angle/2*Mathf.Deg2Rad);
148 }
149 }
150
151 public enum RoofType
152 {
153 Flat,
154 Hipped,
155 Gabled,
156 }
157
158 [Serializable]
159 public class RoofConfig
160 {
161 public RoofType type = RoofType.Flat;
162 public float thickness;
163 public float overhang;
164 }
165 }